#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <sys/vfs.h>
#include <linux/vm_sockets.h>

#define FUN_SUCCESS (0)
#define INVALID_INPARAMS    (-1)
#define MALLOC_FAIL (-2)
#define DATA_POOL_ADD_ITEM_FAIL  (-3)
#define DATA_POOL_REMOVE_ITEM_FAIL  (-4)
#define DATA_POOL_GET_ITEM_FAIL  (-5)
#define DATA_SEND_FAIL  (-6)
#define DATA_RECV_FAIL  (-7)
#define CONNECT_FAIL  (-8)
#define FUNCTION_CALL_FAIL (-9)
#define CONNECT_CLOSE   (-10)
#define BUF_LEN_NOT_ENOUGH      (-11)

typedef enum {
	WAIT,
	SEND,
	FUNIN,
	FUNOUT,
	RECEIVE,
	UNUSUAL,
} status_st;

typedef enum {
        DATA_ID_BEGIN = 0,
        DATA_ID_END = 7,
        DATA_ID_LEN = 8,
        DATA_LEN_BEGIN = 8,
        DATA_LEN_END = 8,
        DATA_LEN_LEN = 1,
        FUN_RET_BEGIN = 9,
        FUN_RET_END = 12,
        FUN_RET_LEN = 4,
        PREV_DATA_LEN = 13,
} data_layout;

typedef struct data_pool {
	uint64_t data_id;
	uint8_t *data;
	size_t data_len;
	struct data_pool *prev;
	struct data_pool *next;
	pthread_cond_t *cond;
	pthread_mutex_t *lock;
	status_st status;
	int ret;
} data_pool_t;

static int64_t counter = 0;
pthread_mutex_t counter_lock = PTHREAD_MUTEX_INITIALIZER;
static data_pool_t *header = NULL;

static uint64_t get_counter(void)
{
	int64_t counter_v;
	(void)pthread_mutex_lock(&counter_lock);
	counter++;
	counter_v = counter;
	(void)pthread_mutex_unlock(&counter_lock);
	return counter_v;
}

static data_pool_t *new_data_item(void)
{
	data_pool_t *item = NULL;
	int ret;

	item = (data_pool_t *)malloc(sizeof(data_pool_t));
	if (item == NULL) {
		return NULL;
	}

	item->cond = (pthread_cond_t *)malloc(sizeof(pthread_cond_t));
	if (item->cond == NULL) {
		goto free_item;
	}

	item->lock = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
	if (item->lock == NULL) {
		goto free_mutex;
	}

	ret = pthread_cond_init(item->cond, NULL);
	if (ret != 0) {
		goto free_mutex;
	}

	ret = pthread_mutex_init(item->lock, NULL);
	if (ret != 0) {
		(void)pthread_cond_destroy(item->cond);
		goto free_mutex;
	}

	return item;

free_mutex:
	free(item->lock);
	item->lock = NULL;
// free_cond:
	free(item->cond);
	item->cond = NULL;
free_item:
	free(item);
	item = NULL;

	return item;
}

static void free_data_item(data_pool_t *item)
{
	if (item != NULL && item->cond != NULL) {
		pthread_cond_destroy(item->cond);
		free(item->cond);
		item->cond = NULL;
	}

	if (item != NULL && item->lock != NULL) {
		pthread_mutex_destroy(item->lock);
		free(item->lock);
		item->lock = NULL;
	}

	if (item != NULL) {
		free(item);
		item = NULL;
	}
}

int pool_header_init(void)
{
	header = new_data_item();
	if (header == NULL) {
		return MALLOC_FAIL;
	}

	header->data = NULL;
	header->data_len = 0;
	header->next = NULL;
	header->prev = NULL;
	header->data_id = 0;
	header->status = UNUSUAL;

	return FUN_SUCCESS;
}

void pool_header_fini(void)
{
	free_data_item(header);
}

static int add_item(data_pool_t *pool_header, void *data, size_t data_len, uint64_t data_id, status_st status)
{
	data_pool_t *new_item = NULL;
	data_pool_t *item = NULL;
	
	if (pool_header == NULL || data == NULL || data_len == 0) {
		return INVALID_INPARAMS;
	}

	new_item = new_data_item();
	if (new_item == NULL) {
		return MALLOC_FAIL;
	}

	new_item->data = data;
	new_item->data_len = data_len;
	new_item->data_id = data_id;
	new_item->status = status;

	(void)pthread_mutex_lock(pool_header->lock);
	item = pool_header;
	while (item->next != NULL) {
		item = item->next;
	}

	item->next = new_item;
	new_item->prev = item;
	new_item->next = NULL;
	(void)pthread_mutex_unlock(pool_header->lock);

	return FUN_SUCCESS;
}

static void del_item(data_pool_t *pool_header, uint64_t data_id)
{
	data_pool_t *item = NULL;

	(void)pthread_mutex_lock(pool_header->lock);
	item = pool_header;
	while (item->data_id != data_id) {
		item = item->next;
	}

	item->prev->next = item->next;
	if (item->next != NULL) {
		item->next->prev = item->prev;
	}

	free_data_item(item);
	(void)pthread_mutex_unlock(pool_header->lock);
}

static data_pool_t *get_item_id(data_pool_t *pool_header, uint64_t data_id)
{
	data_pool_t *item = NULL;

	if (pool_header == NULL) {
		return NULL;
	}

	(void)pthread_mutex_lock(pool_header->lock);
	item = pool_header;
	while (item != NULL) {
		if (item->data_id == data_id) {
			break;
		}
		item = item->next;
	}
	(void)pthread_mutex_unlock(pool_header->lock);

	return item;
}

static data_pool_t *get_item_status(data_pool_t *pool_header, status_st status)
{
	data_pool_t *item = NULL;

	if (pool_header == NULL) {
		return NULL;
	}

	(void)pthread_mutex_lock(pool_header->lock);
	item = pool_header;
	while (item != NULL) {
		if (item->status == status) {
			break;
		}
		item = item->next;
	}
	(void)pthread_mutex_unlock(pool_header->lock);

	return item;
}

static void printf_data(uint8_t *data, size_t data_len)
{
	size_t i;
	printf("data_len: %d\n", (int)data_len);
	for (i = 0; i < data_len; i++) {
		printf("%0x ", data[i]);
	}
	printf("\n");
}

static int socket_fd = 0;

static int handle_client_send(void)
{
	data_pool_t *send_item = NULL;
	char prev_data[PREV_DATA_LEN] = {0};
	int ret;

	send_item = get_item_status(header, WAIT);
	if (send_item != NULL) {
		memcpy(prev_data, &(send_item->data_id), DATA_ID_LEN);
		memcpy(prev_data + DATA_ID_LEN, &(send_item->data_len), DATA_LEN_LEN);

		printf("client send first\n");
		printf_data((uint8_t *)prev_data, PREV_DATA_LEN);
		ret = send(socket_fd, prev_data, PREV_DATA_LEN, 0);
		printf("client send first end\n");
		if (ret < 0) {
			printf("client send first failed\n");
			return DATA_SEND_FAIL;
		}

		if (send_item->data_len != 0) {
			printf("client send second\n");
			printf_data(send_item->data, send_item->data_len);
			ret = send(socket_fd, send_item->data, send_item->data_len, 0);
			printf("client send second end\n");
			if (ret < 0) {
				printf("client send second failed\n");
				return DATA_SEND_FAIL;	
			}
		}

		if (send_item->data != NULL) {
			free(send_item->data);
			send_item->data = NULL;
		}
		send_item->data_len = 0;
		send_item->status = SEND;
	}

	return FUN_SUCCESS;
}

static bool client_send_break = false;
static int client_send(void)
{
    int ret;
    printf("client_send\n");
    while(1) {
        ret = handle_client_send();
        if (client_send_break || ret != FUN_SUCCESS) {
            break;
        }
    }
    printf("client_send end\n");
    return ret;
}

static int handle_client_recv(void)
{
	data_pool_t *item;
	uint64_t data_id;
	char prev_data[PREV_DATA_LEN] = {0};
	int ret;

	printf("client recv first\n");
	ret = recv(socket_fd, prev_data, PREV_DATA_LEN, 0);
	printf_data((uint8_t*)prev_data, PREV_DATA_LEN);
	printf("client recv first end\n");
	if (ret == 0) {
		return CONNECT_CLOSE;
	}
	
	if (ret < 0) {
		printf("client recv first error\n");
		return DATA_RECV_FAIL;
	}

	memcpy(&data_id, prev_data, DATA_ID_LEN);

	item = get_item_id(header, data_id);
	if (item == NULL) {
		printf("item null\n");
		return DATA_POOL_GET_ITEM_FAIL;
	}

	item->data_len = prev_data[DATA_ID_LEN];
	memcpy(&(item->ret), prev_data + DATA_ID_LEN + DATA_LEN_LEN, FUN_RET_LEN);
	
	if (item->data_len != 0) {
		item->data = malloc(item->data_len);
		if (item->data == NULL) {
			return MALLOC_FAIL;
		}
		printf("client recv second\n");
		ret = recv(socket_fd, item->data, item->data_len, 0);
		printf_data(item->data, item->data_len);
		printf("client recv second end\n");
		if (ret < 0) {
			printf("client recv second error\n");
			if (item->data != NULL) {
				free(item->data);
				item->data = NULL;
			}
			return DATA_RECV_FAIL;
		}
	}

	item->status = RECEIVE;

	//(void)pthread_mutex_lock(item->lock);
	pthread_cond_signal(item->cond);
	//(void)pthread_mutex_unlock(item->lock);

	return FUN_SUCCESS;
}

static bool client_recv_break = false;
static int client_recv(void)
{
    int ret;
    printf("client_recv\n");
    while(1) {
        ret = handle_client_recv();
        if (client_recv_break || ret != FUN_SUCCESS) {
            break;
        }
    }
    printf("client_recv end\n");
    return ret;
}

int client_run(unsigned int cid, int port)
{
	int fd;
	int ret;
	pthread_t send_id;
	pthread_t recv_id;
	struct sockaddr_vm server_addr;

	fd = socket(AF_VSOCK, SOCK_STREAM, 0);
	if (fd < 0) {
		return CONNECT_FAIL;
	}

	memset(&server_addr, 0, sizeof(server_addr));
	// server_addr.sin_family = AF_INET;
	// server_addr.sin_port = htons(port);
	// server_addr.sin_addr.s_addr = inet_addr(ip);
    server_addr.svm_family = AF_VSOCK;
    server_addr.svm_cid = cid;
    server_addr.svm_port = port;

	ret = connect(fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (ret < 0) {
        return CONNECT_FAIL;
    }
	socket_fd = fd;

	if (pthread_create(&send_id, NULL, (void *)client_send, NULL) != 0) {
		printf("failed to create pthread for send data\n");
		exit(1);
	}

	if (pthread_create(&recv_id, NULL, (void *)client_recv, NULL) != 0) {
		printf("failed to create pthread for recv data\n");
		exit(1);
	}

	return FUN_SUCCESS;
}

int client_init(unsigned int cid, int port)
{
        int ret;

        ret = pool_header_init();
        if (ret != FUN_SUCCESS) {
                return ret;
        }

        ret = client_run(cid, port);
        if (ret != FUN_SUCCESS) {
                pool_header_fini();
        }

        return ret;
}

static void clear_data_pool(void)
{
	data_pool_t *item = NULL;
	data_pool_t *parent_item = NULL;

	item = header;
	while (item->next != NULL) {
		item = item->next;
	}

	while (item != header) {
		parent_item = item->prev;
		if (item->data != NULL) {
			free(item->data);
			item->data = NULL;
		}
		item->data_len = 0;
		free_data_item(item);
		item = parent_item;
	}
}

void client_fini(void)
{
    client_send_break = true;
    client_recv_break = true;
    close(socket_fd);
    clear_data_pool();
    pool_header_fini();
}

static uint64_t data_send(uint8_t *data, size_t data_len)
{
	uint8_t *item_data = NULL;
	uint64_t ret;
	int64_t data_id;

	if (data_len != 0) {
		item_data = malloc(data_len);
		if (item_data == NULL) {
			return MALLOC_FAIL;
		}
		memcpy(item_data, data, data_len);
	}
	
	data_id = get_counter();
	ret = add_item(header, item_data, data_len, data_id, WAIT);
	if (ret != FUN_SUCCESS) {
		if (item_data != NULL) {
			free(item_data);
			item_data = NULL;
		}
		return DATA_POOL_ADD_ITEM_FAIL;
	}
	
	return data_id;
}

static int data_wait(uint64_t data_id)
{
	data_pool_t *item = NULL;
	
	item = get_item_id(header, data_id);
	if (item == NULL) {
		return DATA_POOL_GET_ITEM_FAIL;
	}

	(void)pthread_mutex_lock(item->lock);
	(void)pthread_cond_wait(item->cond, item->lock);
	(void)pthread_mutex_unlock(item->lock);

	return FUN_SUCCESS;
}

static int data_receive(uint8_t *data, size_t *data_len, uint64_t data_id, int *ret_val)
{
	// int ret;
	// uint8_t *tmp = NULL;
	data_pool_t *item = NULL;

	item = get_item_id(header, data_id);
	if (item == NULL) {
		return DATA_POOL_GET_ITEM_FAIL;
	}

	if (*data_len < item->data_len) {
		return BUF_LEN_NOT_ENOUGH;
	}
	
	*ret_val = item->ret;
	*data_len = item->data_len;
	if (item->data_len != 0) {
		memcpy(data, item->data, item->data_len);
	}

	if (item->data != NULL) {
		free(item->data);
		item->data = NULL;
	}

	item->data_len = 0;

	del_item(header, data_id);

	return FUN_SUCCESS;
}

int client_handle(uint8_t *in_data, size_t in_data_len, uint8_t *out_data, size_t *out_data_len, int *ret_val)
{
	uint64_t data_id;
	int ret;

	printf("data send\n");
	data_id = data_send(in_data, in_data_len);
	// if (data_id < 0) {
	// 	return DATA_SEND_FAIL;
	// }
	
	printf("data wait\n");
	ret = data_wait(data_id);
	if (ret != FUN_SUCCESS) {
		printf("data wait failed\n");
		return ret;
	}

	printf("data receive\n");
	ret = data_receive(out_data, out_data_len, data_id, ret_val);

	printf("data handle ok\n");
	return ret;
}
